<?php
/**
 * Created by Hera Portal.
 * User: Peter Fan(peter.fan@codelocks.hk)
 * Date: 2020/3/24 16:29
 */

/**
 * filter looks like.
 * {"$or":{"field1":"value1","field2":"value2",....}}
 *
 * {"field":{"$gt":"value"}}
 * {"field":{"$like":"value"}}
 * {"field":{"$in":["value1","value2",...]}}
 * {"field":{"$between":["value1","value2"]}}
 * {"field":{"$is":null}}
 * {"field":{"$eq":null}}
 * {"field":{"$not":{"$is":null}}},...
 * {"field":{"$not":{"$like":"value"}}},...
 *
 */

namespace App\Library\Model;



use Illuminate\Database\Eloquent\Builder;

/**
 * Trait WithRequestFilter
 * @package App\Library\Model
 */
trait WithRequestFilter
{
    protected $expression = [
        '$like',
        '$is',
        '$between',
        '$in',
        '$gt',
        '$gte',
        '$lt',
        '$lte',
        '$eq',
        '$not',
    ];
    protected $operator = [
        '$or',
        '$and',
//        '$having',
    ];

    /**
     * @param Builder $query
     * @param array   $filter
     * @throws InvalidParameterException
     */
    protected function setFilter(Builder $query, array $filter)
    {
        foreach ($filter as $key => $value) {
            if (in_array($key, $this->operator)) {
                $operator = $key;
                $this->buildOperator($query, $value, $operator);
            } else {
                $field = $key;
                $this->buildExpression($query, $field, $value);
            }
        }
    }

    /**
     * @param Builder $query
     * @param mixed   $filter
     * @param string  $operator
     * @throws InvalidParameterException
     */
    private function buildOperator(Builder $query, $filter, string $operator = '$and')
    {
        if ($operator === '$or') {
            $query->orWhere(function ($query) use ($filter) {
                $this->buildOperator($query, $filter);
            });
        } else {
            foreach ($filter as $key => $value) {
                if (in_array($key, $this->operator)) {
                    $operator = $key;
                    $this->buildOperator($query, $value, $operator);
                } else {
                    $field = $key;
                    $this->buildExpression($query, $field, $value);
                }
            }
        }
    }

    /**
     * @param Builder $query
     * @param string  $fieldName
     * @param mixed   $fieldValue
     * @param bool    $not
     * @throws InvalidParameterException
     */
    private function buildExpression(Builder $query, string $fieldName, $fieldValue, $not = false)
    {
        if (is_array($fieldValue)) {
            foreach ($fieldValue as $expression => $value) {
                if ($expression === '$not') {
                    $this->buildExpression($query, $fieldName, $value, true);
                } else {
                    $this->buildCondition($query, $fieldName, $value, $expression, $not);
                }
            }
        } else {
            $this->buildCondition($query, $fieldName, $fieldValue, '$eq', $not);
        }

    }

    /**
     * @param Builder $query
     * @param string  $field
     * @param mixed   $value
     * @param string  $expression
     * @param bool    $not
     * @throws InvalidParameterException
     */
    private function buildCondition(Builder $query, $field, $value, $expression, bool $not = false)
    {
        switch ($expression) {
            case '$is':
                if (is_null($value)) {
                    $not ? $query->whereNotNull($field) : $query->whereNull($field);
                } else {
                    throw new InvalidParameterException('The value of key "$is" must be null');
                }
                break;
            case '$between':
                if (is_array($value) && count($value) === 2) {
                    $not ? $query->whereNotBetween($field, $value) : $query->whereBetween($field, $value);
                } else {
                    throw new InvalidParameterException('The value of key "$between" must be an array of length 2');
                }
                break;
            case '$in':
                if (is_array($value)) {
                    $not ? $query->whereNotIn($field, $value) : $query->whereIn($field, $value);
                } else {
                    $not ? $query->whereNotIn($field, [$value]) : $query->whereIn($field, [$value]);
                }
                break;
            case '$gt':
                $not ? $query->where($field, '<=', $value) : $query->where($field, '>', $value);
                break;
            case '$gte':
                $not ? $query->where($field, '<', $value) : $query->where($field, '>=', $value);
                break;
            case '$lt':
                $not ? $query->where($field, '>=', $value) : $query->where($field, '<', $value);
                break;
            case '$lte':
                $not ? $query->where($field, '>', $value) : $query->where($field, '<=', $value);
                break;
            case '$eq':
                $not ? $query->where($field, '<>', $value) : $query->where($field, $value);
                break;
            default:
                if (method_exists($this, 'buildHandler')) {
                    call_user_func([$this, 'buildHandler'], $query, $field, $value, $expression, $not);
                } elseif (is_array($value)) {
                    throw new InvalidParameterException("the value of field $field can't be an array");
                } elseif (is_null($value)) {
                    $not ? $query->whereNull($field) : $query->whereNotNull($field);
                } else {
                    $not ? $query->where($field, '<>', $value) : $query->where($field, $value);
                }
        }
    }

}
